package ling.learn.syntax;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import ling.learn.template.CodeGen;

public class AntlrGrammarWriter {
  private int ruleIdMaxLen = 23;
  private String ruleIdSpaces = "";

  public AntlrGrammarWriter() {
    ruleIdSpaces = String.join("", Collections.nCopies(30, " "));
  }

  public void write(String dirPath, String grammarName,
      Map<String, Map<String, String>> rulesTypeMap) throws IOException {
    Map<String, Object> dataModel = new HashMap<>();
    dataModel.put("grammarName", grammarName);

    for (Entry<String, Map<String, String>> entry : rulesTypeMap.entrySet()) {
      String type = entry.getKey();
      Map<String, String> rules = entry.getValue();
      String ruleText = joinRules(type, rules);
      dataModel.put(type, ruleText);
    }

    CodeGen g4CodeGen = new CodeGen();
    g4CodeGen.execute(dirPath + grammarName + ".g4", grammarName + ".g4.ftl", dataModel);
  }

  private String getRuleId(String oldRuleId) {
    String newRuleId = "";
    int ruleIdLen = oldRuleId.length();
    byte[] bytes;
    try {
      bytes = oldRuleId.getBytes("MS932");
      ruleIdLen = bytes.length;
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
    if (ruleIdLen < ruleIdMaxLen) {
      newRuleId = oldRuleId + ruleIdSpaces;
      newRuleId = newRuleId.substring(0, ruleIdMaxLen - ruleIdLen + oldRuleId.length());
    } else {
      newRuleId = oldRuleId;
    }
    return newRuleId;
  }

  private String getRule(String id, String text) {
    return getRuleId(id) + " : " + text + " ;\r\n";
  }

  private boolean exclude(String id) {
    if (id.equals("other")) {
      return true;
    }
    return false;
  }

  private String joinRules(final String type, final Map<String, String> rules) {
    StringBuilder stream = new StringBuilder();

    if (rules.isEmpty()) {
      return "";
    }

    String typeId = "";
    String typeText = "";
    String ruleList = "";

    Map<String, String> treeMap = new TreeMap<>(new Comparator<String>() {

      @Override
      public int compare(String o1, String o2) {
        if (type.equals("operator")) {
          String v1 = rules.getOrDefault(o1, o1);
          String v2 = rules.getOrDefault(o2, o2);
          v1 = v1.substring(1, v1.length() - 1);
          v2 = v2.substring(1, v2.length() - 1);
          if (v1.length() != v2.length() && (v1.startsWith(v2) || v2.startsWith(v1))) {
            return Integer.compare(v2.length(), v1.length());
          }
          return v1.compareTo(v2);
        } else if (o1.length() != o2.length() && (o1.startsWith(o2) || o2.startsWith(o1))) {
          return Integer.compare(o2.length(), o1.length());
        }
        return o1.compareTo(o2);
      }
    });

    treeMap.putAll(rules);
    String spaces = "";
    if (!exclude(type)) {
      typeId = getRuleId(type);
      spaces = String.join("", Collections.nCopies(typeId.length(), " "));
    }
    for (Map.Entry<String, String> entry : treeMap.entrySet()) {
      String id = entry.getKey();
      String text = entry.getValue();

      if (!text.isEmpty()) {
        ruleList += getRule(id, text);
      }
      if (!exclude(type)) {
        if (!typeText.isEmpty()) {
          typeText += spaces + " | " + id + "\r\n";
        } else {
          typeText += id + "\r\n";
        }
      }
    }
    if (!exclude(type)) {
      stream.append(
          "//----------------------------------------------------------------------------\r\n");
      stream.append("// ");
      stream.append(typeId.toUpperCase());
      stream.append("\r\n");
      stream.append(
          "//----------------------------------------------------------------------------\r\n");
      stream.append(typeId);
      stream.append(" : ");
      stream.append(typeText);
      stream.append(spaces + " ;\r\n");
    }
    stream.append(ruleList);
    return stream.toString();
  }
}
